<!DOCTYPE html>
<body>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="resources/gamepad-helpers.js"></script>
<script>

function testGamepadStateOneConnected() {
    // To pass this test, Gamepad 0 should have the values set in
    // connectGamepads.
    let gamepad = navigator.getGamepads()[0];
    assert_equals(gamepad.id, 'MockStick 3000');
    assert_equals(gamepad.buttons.length, 2);
    assert_equals(gamepad.buttons[0].value, 1.0);
    assert_true(gamepad.buttons[0].pressed);
    assert_equals(gamepad.buttons[1].value, 0.0);
    assert_false(gamepad.buttons[1].pressed);
    assert_equals(gamepad.axes.length, 2);
    assert_equals(gamepad.axes[0], 0.5);
    assert_equals(gamepad.axes[1], -1.0);
}

function testNoChangeReturnsSameObjects() {
    // Check that accessing gamepad state fetches the same objects until their
    // values change.
    assert_equals(navigator.getGamepads(),
                  navigator.getGamepads(), 'gamepad arrays differ');
    assert_equals(navigator.getGamepads()[0],
                  navigator.getGamepads()[0], 'gamepad objects differ');
    assert_equals(navigator.getGamepads()[0].axes,
                  navigator.getGamepads()[0].axes, 'axes arrays differ');
    assert_equals(navigator.getGamepads()[0].buttons,
                  navigator.getGamepads()[0].buttons, 'gamepad buttons differ');
}

function testSameValueUpdateReturnsSameObjects() {
    // Test that updates with the same value do not cause a new gamepad object
    // to be returned.
    let gamepadBefore = navigator.getGamepads()[0];
    gamepadController.setAxisCount(0, gamepadBefore.axes.length);
    gamepadController.setButtonCount(0, gamepadBefore.buttons.length);
    let gamepadAfter = navigator.getGamepads()[0];
    assert_equals(
        gamepadBefore, gamepadAfter,
        "expected same gamepad after same-value setAxisCount/setButtonCount");
    assert_equals(
        gamepadBefore.timestamp, gamepadAfter.timestamp,
        "expected same timestamp after same-value setAxisCount/setButtonCount");
    assert_equals(
        gamepadBefore.axes, gamepadAfter.axes,
        "expected same axes after same-value setAxisCount/setButtonCount");
    assert_equals(
        gamepadBefore.buttons, gamepadAfter.buttons,
        "expected same buttons after same-value setAxisCount/setButtonCount");

    gamepadBefore = navigator.getGamepads()[0];
    gamepadController.setAxisData(0, 0, gamepadBefore.axes[0]);
    gamepadController.setButtonData(0, 1, gamepadBefore.buttons[1].value);
    gamepadAfter = navigator.getGamepads()[0];
    assert_equals(
        gamepadBefore, gamepadAfter,
        "expected same gamepad after same-value setAxisData/setButtonData");
    assert_equals(
        gamepadBefore.timestamp, gamepadAfter.timestamp,
        "expected same timestamp after same-value setAxisData/setButtonData");
    assert_equals(
        gamepadBefore.axes, gamepadAfter.axes,
        "expected same axes after same-value setAxisData/setButtonData");
    assert_equals(
        gamepadBefore.buttons, gamepadAfter.buttons,
        "expected same buttons after same-value setAxisData/setButtonData");
}

function testDifferentValueUpdateReturnsNewObjects() {
    // Test that changing an axis value causes a new gamepad object to be
    // returned.
    // TODO(crbug.com/855760): Check that the buttons array is preserved.
    gamepadBefore = navigator.getGamepads()[0];
    gamepadController.setAxisData(0, 0, gamepadBefore.axes[0] + 0.1);
    gamepadAfter = navigator.getGamepads()[0];
    assert_not_equals(gamepadBefore, gamepadAfter,
                      "expected new gamepad after new-value setAxisData");
    assert_not_equals(gamepadBefore.timestamp,
                      gamepadAfter.timestamp,
                      "expected new timestamp after new-value setAxisData");
    assert_not_equals(gamepadBefore.axes, gamepadAfter.axes,
                      "expected new axes after new-value setAxisData");

    // Test that changing a button value causes a new gamepad object to be
    // returned.
    // TODO(crbug.com/855760): Check that the axes array is preserved.
    gamepadBefore = navigator.getGamepads()[0];
    gamepadController.setButtonData(0, 0, gamepadBefore.buttons[0].value + 0.1);
    gamepadAfter = navigator.getGamepads()[0];
    assert_not_equals(gamepadBefore, gamepadAfter,
                      "expected new gamepad after new-value setButtonData");
    assert_not_equals(gamepadBefore.timestamp,
                      gamepadAfter.timestamp,
                      "expected new timestamp after new-value setButtonData");
    assert_not_equals(gamepadBefore.buttons, gamepadAfter.buttons,
                      "expected new buttons after new-value setButtonData");

    // Test that changing a button value and an axis value causes a new gamepad
    // object to be returned.
    gamepadBefore = navigator.getGamepads()[0];
    gamepadController.setAxisData(0, 0, gamepadBefore.axes[0] - 0.1);
    gamepadController.setButtonData(0, 0, gamepadBefore.buttons[0].value - 0.1);
    gamepadAfter = navigator.getGamepads()[0];
    assert_not_equals(
        gamepadBefore, gamepadAfter,
        "expected new gamepad after new-value setAxisData/setButtonData");
    assert_not_equals(
        gamepadBefore.timestamp, gamepadAfter.timestamp,
        "expected new timestamp after new-value setAxisData/setButtonData");
    assert_not_equals(
        gamepadBefore.axes, gamepadAfter.axes,
        "expected new axes after new-value setAxisData/setButtonData");
    assert_not_equals(
        gamepadBefore.buttons, gamepadAfter.buttons,
        "expected new buttons after new-value setAxisData/setButtonData");
}

promise_test(async () => {
    // First disconnect all gamepads.
    disconnectGamepads();
    testGamepadStateAllDisconnected();

    // Simulate a gamepad connection and verify the state changes as expected.
    let connectPromise = ongamepadconnected();
    connectGamepads(1);
    await connectPromise;
    testGamepadStateOneConnected();

    // Chrome has different internal behavior depending on whether a gamepad
    // event listener is registered. Exercise both paths to verify that the
    // state changes are handled correctly in either case.
    let disconnectListener = (e) => {};

    window.addEventListener('gamepaddisconnected', disconnectListener);
    testNoChangeReturnsSameObjects();
    testSameValueUpdateReturnsSameObjects();
    testDifferentValueUpdateReturnsNewObjects();

    window.removeEventListener('gamepaddisconnected', disconnectListener);
    testNoChangeReturnsSameObjects();
    testSameValueUpdateReturnsSameObjects();
    testDifferentValueUpdateReturnsNewObjects();

    // Disconnect all gamepads to end the test.
    let disconnectPromise = ongamepaddisconnected();
    disconnectGamepads();
    await disconnectPromise;
    testGamepadStateAllDisconnected();
}, "Typical polling access to gamepads contents.");

</script>
</body>
